#include "znx_udp_proc.h"
#include "znx_worker_proc.h"


typedef struct {
    znx_udp_connect_handler_pt          connect_handler;
    void                                *user_data;
    void                                *conn_data;
} znx_udp_connect_data_t;


void
znx_udp_connect_done(znx_conn_t *conn, znx_result_t res)
{
    znx_udp_connect_data_t          *data;
    znx_udp_connect_handler_pt      connect_handler;
    void                            *user_data;

    data = conn->data;
    conn->data = data->conn_data;
    connect_handler = data->connect_handler;
    user_data = data->user_data;
    znx_thread_free(data);

    if (res == ZNX_SUCCESS) {
        znx_atomic_int64_add_fetch(&server_conns, 1);
        conn->server = 1;
    } else {
        if (conn->fd != INVALID_FD) {
            znx_syscall_close(conn->fd);
            conn->fd = INVALID_FD;
        }
    }

    connect_handler(user_data, conn, res);
}


void
znx_udp_connect_setup(znx_conn_t *conn,
    znx_address_t *address, znx_address_t *bind_address,
    znx_udp_connect_handler_pt connect_handler, void *user_data)
{
    assert(current_thread_is_worker_thread());

    int                     fd;
    znx_result_t            ret;
    znx_udp_connect_data_t  *data;

    data = znx_thread_calloc(sizeof(znx_udp_connect_data_t));
    data->connect_handler = connect_handler;
    data->user_data = user_data;
    data->conn_data = conn->data;
    conn->data = data;

    ret = znx_syscall_socket(address->sockaddr.sa_family, SOCK_DGRAM, 0);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "socket() failed, errno: %d", -ret);
        znx_udp_connect_done(conn, ZNX_FAILED);
        return;
    }

    fd = (int)ret;
    conn->fd = fd;

    ret = znx_syscall_set_nonblock(fd);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "setnonblock %d failed, errno: %d", fd, -ret);
        znx_udp_connect_done(conn, ZNX_FAILED);
        return;
    }

    ret = znx_syscall_set_close_exec(fd);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "setcloseexec %d failed, errno: %d", fd, -ret);
        znx_udp_connect_done(conn, ZNX_FAILED);
        return;
    }

    if (bind_address != NULL) {
        ret = znx_syscall_bind(fd, &bind_address->sockaddr,
            (socklen_t)znx_address_socklen(bind_address));
        if (ret < 0) {
            ZNX_DEF_LOG_ERROR(NULL, "bind %d local address failed, errno: %d", fd, -ret);
            znx_udp_connect_done(conn, ZNX_FAILED);
            return;
        }
    }

    ret = znx_syscall_connect(fd, &address->sockaddr, (socklen_t)znx_address_socklen(address));
    // connect success
    if (ret < 0) {
        znx_udp_connect_done(conn, ZNX_FAILED);
        return;
    }

    znx_udp_connect_done(conn, ZNX_SUCCESS);
}


